#!/usr/bin/env ruby
#
# offset_rprec
#
# Apply an offset to precision values in an rprec configuration.
#

$TYPE_APPLICATION = "APPLICATION"                   # control point levels
$TYPE_MODULE      = "MODULE"
$TYPE_FUNCTION    = "FUNC"
$TYPE_BASICBLOCK  = "BBLK"
$TYPE_INSTRUCTION = "INSN"

$TYPE_SPACES = Hash.new("")
$TYPE_SPACES[$TYPE_APPLICATION] = ""                # for pretty output
$TYPE_SPACES[$TYPE_MODULE]      = "  "
$TYPE_SPACES[$TYPE_FUNCTION]    = "    "
$TYPE_SPACES[$TYPE_BASICBLOCK]  = "      "
$TYPE_SPACES[$TYPE_INSTRUCTION] = "        "

$STATUS_NONE      = " "
$STATUS_SINGLE    = "s"
$STATUS_DOUBLE    = "d"
$STATUS_RPREC     = "r"

$COUNT_COMMON     = "Common"
$COUNT_ADDITIONS  = "Additions"
$COUNT_DELETIONS  = "Deletions"
$COUNT_HIGHER     = "Higher"
$COUNT_LOWER      = "Lower"
$COUNT_IDENTICAL  = "Identical"

$num_zeroes = 0

class PPoint
    attr_accessor :uid              # unique regex identifier; e.g. "INSN #34: 0x804d3f"
    attr_accessor :id               # program component identifier; e.g. 34 in line above
    attr_accessor :type             # module, function, instruction, etc.
    attr_accessor :orig_status      # single, double, ignore, candidate, none
    attr_accessor :diff_status      # single, double, ignore, candidate, none
    attr_accessor :precision        # precision level
    attr_accessor :parent           # PPoint
    attr_accessor :children         # array of PPoints
    attr_accessor :attrs            # string => string
    attr_accessor :byid             # id => PPoint          (for increased lookup speed)

    def initialize (uid, type, orig_status)
        @uid = uid
        @id = 0
        if @uid =~ /#(\d+)/ then
            @id = $1.to_i
        end
        @type = type
        @orig_status = orig_status
        @diff_status = $STATUS_NONE
        @precision = 52
        @parent = nil
        @children = Array.new
        @attrs = Hash.new
        @byid = Hash.new
    end

    def flatten_to_insns
        flatten(nil)
    end

    def flatten(overload_prec)
        #puts "flattening #{to_s}: #{@orig_status} #{overload_prec}"
        if @type == $TYPE_INSTRUCTION then
            if @orig_status != $STATUS_RPREC and not overload_prec.nil? then
                #puts " APPLY OVERLOAD: #{overload_prec}"
                @precision = overload_prec
                @orig_status = $STATUS_RPREC
            end
        else
            if @orig_status == $STATUS_RPREC then
                #puts " BEGIN OVERLOAD: #{@precision}"
                overload_prec = @precision
            end

            # clear out statuses for all non-instruction nodes
            @orig_status = $STATUS_NONE
        end
        @children.each do |pt|
            pt.flatten(overload_prec)
        end
    end

    def apply_offset (offset)

        # TEMPORARY - bump zero up to 52
        # TODO: remove this
        if not @precision.nil? and @precision == 0 then
            @precision = 52
            $num_zeroes += 1
        end
        
        @precision += offset if not @precision.nil?
        @precision = 52 if @precision > 52
        @precision = 0 if @precision < 0
        @children.each do |c|
            c.apply_offset(offset)
        end
    end

    def build_config_file (fn)
        # write out a configuration file for this config
        # this method will only be called in an APPLICATION node; it calls
        # output() to recursively print the other levels
        fout = File.new(fn, "w")
        if @attrs.has_key?("prolog") then
            @attrs["prolog"].each do |line|
                fout.puts line
            end
        end
        output_all(fout)
        fout.close
    end

    def output_all(fout)
        prec_line = nil
        fout.print("^")
        if type == $TYPE_INSTRUCTION then
            if @orig_status == $STATUS_RPREC then
                prec_line = "#{type}_#{@id}_precision=#{@precision}"
            end
            fout.print(@orig_status)
        end
        fout.print(" ")
        fout.print($TYPE_SPACES[@type])     # indentation
        fout.print(@uid)
        if @attrs.has_key?("desc") then
            fout.print(" \"")
            fout.print(@attrs["desc"])
            fout.print("\"")
        end
        fout.print("\n")
        if not prec_line.nil? then
            fout.puts prec_line
        end
        @children.each do |pt|
            pt.output_all(fout)
        end
    end

    def to_s
        prec_line = nil
        str = "^#{@orig_status} #{$TYPE_SPACES[@type]} #{@uid}"
        if @attrs.has_key?("desc") then
            str += " \"#{@attrs["desc"]}\""
        end
        if not prec_line.nil? then
            str += "\n#{prec_line}"
        end
        return str
    end
end


def load_config(fn)
    program = nil
    mod = nil
    func = nil
    bblk = nil
    prolog = ""
    IO.foreach(fn) do |line|
        if line =~ /(APPLICATION #\d+: [x0-9A-Fa-f]+) \"(.+)\"/ then
            program = PPoint.new($1, $TYPE_APPLICATION, line[1,1])
            program.attrs["desc"] = $2
            program.byid["APPLICATION #{program.id}"] = program
        elsif line =~ /(MODULE #\d+: 0x[0-9A-Fa-f]+) \"(.+)\"/ then
            mod = PPoint.new($1, $TYPE_MODULE, line[1,1])
            mod.attrs["desc"] = $2
            program.children << mod if program != nil
            program.byid["MODULE #{mod.id}"] = mod
        elsif line =~ /(FUNC #\d+: 0x[0-9A-Fa-f]+) \"(.+)\"/ then
            func = PPoint.new($1, $TYPE_FUNCTION, line[1,1])
            func.attrs["desc"] = $2
            mod.children << func if mod != nil
            program.byid["FUNC #{func.id}"] = func
        elsif line =~ /(BBLK #\d+: (0x[0-9A-Fa-f]+))/ then
            bblk = PPoint.new($1, $TYPE_BASICBLOCK, line[1,1])
            bblk.attrs["addr"] = $2
            func.children << bblk if func != nil
            program.byid["BBLK #{bblk.id}"] = bblk
        elsif line =~ /(INSN #\d+: (0x[0-9A-Fa-f]+)) \"(.+)\"/ then
            insn = PPoint.new($1, $TYPE_INSTRUCTION, line[1,1])
            insn.attrs["addr"] = $2
            insn.attrs["desc"] = $3
            bblk.children << insn if bblk != nil
            program.byid["INSN #{insn.id}"] = insn
        elsif line =~ /(\w+)_(\d+)_precision=(\d+)/ then
            tag = $1
            id = $2.to_i
            prec = $3.to_i
            pt = program.byid["#{tag} #{id}"]
            if pt.nil? then
                puts "Cannot find point #{tag} \##{id}"
            else
                pt.precision = prec
            end
        else
            prolog << line.chomp
            prolog << "\n"
        end
    end
    program.attrs["prolog"] = prolog
    if program.nil? then
        puts "Unable to load file: #{fn}"
        exit
    end
    return program
end


fn = nil
offset = nil

ARGV.each do |arg|
    if arg == "-h" then
        puts "Usage: offset_rprec <file> <offset>"
        exit
    elsif fn.nil? then
        fn = arg
    elsif offset.nil? then
        offset = arg.to_i
    end
end

cfg = load_config(fn)
cfg.flatten_to_insns
cfg.apply_offset(offset)
cfg.build_config_file("output.cfg")

zf = File.open("zeroes.txt", "w")
zf.puts $num_zeroes.to_s
zf.close

